/*************************************************************************/
/*                                                                       */
/*                Centre for Speech Technology Research                  */
/*                     University of Edinburgh, UK                       */
/*                        Copyright (c) 1999                             */
/*                        All Rights Reserved.                           */
/*                                                                       */
/*  Permission is hereby granted, free of charge, to use and distribute  */
/*  this software and its documentation without restriction, including   */
/*  without limitation the rights to use, copy, modify, merge, publish,  */
/*  distribute, sublicense, and/or sell copies of this work, and to      */
/*  permit persons to whom this work is furnished to do so, subject to   */
/*  the following conditions:                                            */
/*   1. The code must retain the above copyright notice, this list of    */
/*      conditions and the following disclaimer.                         */
/*   2. Any modifications must be clearly marked as such.                */
/*   3. Original authors' names are not deleted.                         */
/*   4. The authors' names are not used to endorse or promote products   */
/*      derived from this software without specific prior written        */
/*      permission.                                                      */
/*                                                                       */
/*  THE UNIVERSITY OF EDINBURGH AND THE CONTRIBUTORS TO THIS WORK        */
/*  DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING      */
/*  ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT   */
/*  SHALL THE UNIVERSITY OF EDINBURGH NOR THE CONTRIBUTORS BE LIABLE     */
/*  FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES    */
/*  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN   */
/*  AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,          */
/*  ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF       */
/*  THIS SOFTWARE.                                                       */
/*                                                                       */
/*************************************************************************/
/*             Author :  Alan W Black (awb@cstr.ed.ac.uk)                */
/*             Date   :  March 1999                                      */
/*             Modified: Hynek Hanke (hanke@brailcom.org) (2003-2005)    */
/*-----------------------------------------------------------------------*/
/*                                                                       */
/* Client end of Festival server API in C designed specifically formerly */
/* for Galaxy Communicator use, but rewritten to suit Speech Dispatcher  */
/* needs. Please look also at the original festival_client.c library     */
/* that can be found in festival/examples/festival_client.c -- it will   */
/* be probably more up-to-date.                                          */
/*                                                                       */
/* This can be also compiled as a stand-alone program for testing        */
/* purposes:                                                             */
/*    gcc -o festival_client -DSTANDALONE festival_client.c              */
/* and run as:                                                           */
/*    ./festival_client                                                  */
/* This creates a file test.snd, containing the synthesized waveform     */
/* You can run for example:                                              */
/*    play test.snd                                                      */
/* to hear the message                                                   */
/*=======================================================================*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>

/* I'm including my local .h, not the Alan's one! */
#include "festival_client.h"

/* For testing endianness */
int fapi_endian_loc = 1;

static char *socket_receive_file_to_buff(int fd,int *size);


/* --- MANAGING FT STRUCTURES --- */

void delete_FT_Wave(FT_Wave *wave)
{
    if (wave != 0)
        {
            if (wave->samples != 0)
                free(wave->samples);
            free(wave);
        }
}

int save_FT_Wave_snd(FT_Wave *wave, const char *filename)
{
    FILE *fd;
    struct {
	unsigned int    magic;	/* magic number */
	unsigned int    hdr_size;	/* size of this header */
	int             data_size;        /* length of data (optional) */
	unsigned int    encoding;	/* data encoding format */
	unsigned int    sample_rate; /* samples per second */
	unsigned int    channels;	 /* number of interleaved channels */
    } header;
    short sw_short;
    int i;

    if ((filename == 0) ||
	(strcmp(filename,"stdout") == 0) ||
	(strcmp(filename,"-") == 0))
	fd = stdout;
    else if ((fd = fopen(filename,"wb")) == NULL)
        {
            fprintf(stderr,"save_FT_Wave: can't open file \"%s\" for writing\n",
                    filename);
            return -1;
        }
    
    header.magic = (unsigned int)0x2e736e64;
    header.hdr_size = sizeof(header);
    header.data_size = 2 * wave->num_samples;
    header.encoding = 3; /* short */
    header.sample_rate = wave->sample_rate;
    header.channels = 1;
    if (FAPI_LITTLE_ENDIAN)
        {   /* snd is always sparc/68000 byte order */
            header.magic = SWAPINT(header.magic);
            header.hdr_size = SWAPINT(header.hdr_size);
            header.data_size = SWAPINT(header.data_size);
            header.encoding = SWAPINT(header.encoding);
            header.sample_rate = SWAPINT(header.sample_rate);
            header.channels = SWAPINT(header.channels);
        }

    /* write header */
    if (fwrite(&header, sizeof(header), 1, fd) != 1)
	return -1;
    if (FAPI_BIG_ENDIAN)
	fwrite(wave->samples,sizeof(short),wave->num_samples,fd);
    else
        {  /* have to swap */
            for (i=0; i < wave->num_samples; i++)
                {
                    sw_short = SWAPSHORT(wave->samples[i]);
                    fwrite(&sw_short,sizeof(short),1,fd);
                }
        }

    if (fd != stdout)
	fclose(fd);
    return 0;
}

void delete_FT_Info(FT_Info *info)
{
    if (info != 0)
	free(info);
}

/* --- FESTIVAL REPLY PARSING --- */

static int
nist_get_param_int(char *hdr, char *field, int def_val)
{
    char *p;
    int val;

    if (((p=strstr(hdr,field)) != NULL) &&
	(strncmp(" -i ",p+strlen(field),4) == 0))
        {
            sscanf(p+strlen(field)+4,"%d",&val);
            return val;
        }
    else
	return def_val;
}

static int
nist_require_swap(char *hdr)
{
    char *p;
    char *field = "sample_byte_format";

    if ((p=strstr(hdr,field)) != NULL)
        {
            if (((strncmp(" -s2 01",p+strlen(field),7) == 0) && FAPI_BIG_ENDIAN) ||
                ((strncmp(" -s2 10",p+strlen(field),7) == 0) 
                 && FAPI_LITTLE_ENDIAN))
                return 1;
        }
    return 0; /* if unknown assume native byte order */
}

/* --- FESTIVAL SOCKET MANIPULATION --- */

/* Return an FD to a remote server */
static int
festival_socket_open(const char *host, int port)
{   
    struct sockaddr_in serv_addr;
    struct hostent *serverhost;
    int fd;

    fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (fd < 0)  
        {
            fprintf(stderr,"festival_client: can't get socket\n");
            return -1;
        }
    memset(&serv_addr, 0, sizeof(serv_addr));
    if ((serv_addr.sin_addr.s_addr = inet_addr(host)) == -1)
        {
            /* its a name rather than an ipnum */
            serverhost = gethostbyname(host);
            if (serverhost == (struct hostent *)0)
                {
                    fprintf(stderr,"festival_client: gethostbyname failed\n");
                    return -1;
                }
            memmove(&serv_addr.sin_addr,serverhost->h_addr, serverhost->h_length);
        }
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(port);

    if (connect(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) != 0)
        {
            fprintf(stderr,"festival_client: connect to server failed\n");
            return -1;
        }

    return fd;
}

/* Receive file (probably a waveform file) from socket using   */
/* Festival key stuff technique, but long winded I know, sorry */
/* but will receive any file without closing the stream or     */
/* using OOB data                                              */
static char*
socket_receive_file_to_buff(int fd,int *size)
{
    static char *file_stuff_key = "ft_StUfF_key"; /* must == Festival's key */
    char *buff;
    int bufflen;
    int n,k,i;
    char c;

    if (fd < 0) return NULL;

    bufflen = 1024;
    buff = (char *)malloc(bufflen);
    *size=0;

    for (k=0; file_stuff_key[k] != '\0';)
    {
	n = read(fd,&c,1);
	if (n<=0){
            DBG("ERROR: FESTIVAL CLOSED CONNECTION (1)");
	    close(fd);
            festival_connection_crashed = 1;
	    xfree(buff);
	    return NULL;  /* hit stream eof before end of file */
	}

	if ((*size)+k+1 >= bufflen)
	{   /* +1 so you can add a NULL if you want */
	    bufflen += bufflen/4;
	    buff = (char *)realloc(buff,bufflen);
	}
	if (file_stuff_key[k] == c)
	    k++;
	else if ((c == 'X') && (file_stuff_key[k+1] == '\0'))
	{   /* It looked like the key but wasn't */
	    for (i=0; i < k; i++,(*size)++) 
		buff[*size] = file_stuff_key[i];
	    k=0;
	    /* omit the stuffed 'X' */
	}
	else
	{
	    for (i=0; i < k; i++,(*size)++)
		buff[*size] = file_stuff_key[i];
	    k=0;
	    buff[*size] = c;
	    (*size)++;
	}

    }

    return buff;
}

static char *
client_accept_s_expr(int fd)
{
    /* Read s-expression from server, as a char * */
    char *expr;
    int filesize;

    if (fd < 0) return NULL;
 
    expr = socket_receive_file_to_buff(fd,&filesize);
    expr[filesize] = '\0';
    return expr;
}

static FT_Wave *client_accept_waveform(int fd, int *stop_flag, int stop_by_close)
{
    /* Read waveform from server */
    char *wavefile;
    int filesize;
    int num_samples, sample_rate, i;
    FT_Wave *wave;

    if (fd < 0) return NULL;

    wavefile = socket_receive_file_to_buff(fd, &filesize);
    if (wavefile == NULL) return NULL;

    wave = NULL;
    
    /* I know this is NIST file and its an error if it isn't */
    if (filesize >= 1024)
        {
            /* If this doesn't work, probably you forgot to set
             the output file type to NIST ! by Parameter.set */
            num_samples = nist_get_param_int(wavefile,"sample_count",1);
            sample_rate = nist_get_param_int(wavefile,"sample_rate",16000);

            if ((num_samples*sizeof(short))+1024 == filesize)
                {
                    wave = (FT_Wave *)malloc(sizeof(FT_Wave));
		    DBG("Number of samples from festival: %d", num_samples);
                    wave->num_samples = num_samples;
                    wave->sample_rate = sample_rate;
		    if (num_samples != 0){
			wave->samples = (short *) malloc(num_samples*sizeof(short));
			memmove(wave->samples, wavefile+1024, num_samples*sizeof(short));
			if (nist_require_swap(wavefile))
			    for (i=0; i < num_samples; i++)
				wave->samples[i] = SWAPSHORT(wave->samples[i]);
		    }else{
			wave->samples = NULL;
		    }
                }
        }
    
    if (wavefile != NULL)  /* just in case we've got an ancient free() */
	free(wavefile);

    return wave;
}

int
festival_get_ack(FT_Info **info, char* ack)
{
    int read_bytes;
    int n;

    if (*info == NULL) return -1;
    if ((*info)->server_fd < 0) return -1;

    for (n=0; n < 3; ){
        read_bytes = read((*info)->server_fd, ack+n, 3-n);
        if (read_bytes <= 0){
            /* WARNING: This is a very strange situation
               but it happens often, I don't really know
               why???*/
            DBG("ERROR: FESTIVAL CLOSED CONNECTION (2)");
	    close((*info)->server_fd);
            festival_connection_crashed = 1;
            return -1; 
        }
        n += read_bytes;                
    }
    ack[3] = '\0';
    return 0;
}

int
festival_read_response(FT_Info *info, char **expr)
{
    char buf[4];
    char *r;

    DBG("Com: Reading response");

    if (info == NULL) return 1;
    if (info->server_fd < 0) return 1;

    if (festival_get_ack(&info, buf)) return 1;
    buf[3] = 0;

    DBG("<- Festival: |%s|", buf);

    if (!strcmp(buf,"ER\n")){
        if (expr != NULL) *expr = NULL;
        return 1;
    }else{
        if (expr != NULL){
            *expr = client_accept_s_expr(info->server_fd);
        }else{
            r = client_accept_s_expr(info->server_fd);
	    if (r != NULL) free(r);
	}
    }

    if (festival_get_ack(&info, buf)) return 1;
    DBG("<- Festival: |%s|", buf);

    return 0;
}

int
festival_accept_any_response(FT_Info *info)
{
    char ack[4];
    int r;
    char *expr;

    DBG("Com: Accepting any response");
    do {
      if( (r = festival_get_ack(&info, ack)) ) return r;
        DBG("<- Festival: |%s|",ack);
	if (strcmp(ack,"WV\n") == 0){         /* receive a waveform */
	    client_accept_waveform(info->server_fd, NULL, 0);
	}else if (strcmp(ack,"LP\n") == 0){    /* receive an s-expr */
	    expr = client_accept_s_expr(info->server_fd);
	    if (expr != NULL) free(expr);
	}else if (strcmp(ack,"ER\n") == 0)    /* server got an error */
            {
                /* This message ER is returned even if it was because there was
                   no sound produced, for this reason, the warning is disabled */
                /* fprintf(stderr,"festival_client: server returned error\n"); */
                break;
            }
    }while (strcmp(ack,"OK\n") != 0);

    return 0;
}

#define FEST_SEND_CMD(format) \
    { \
        FILE *fd; \
        char *str; \
        fd = fdopen(dup(info->server_fd),"wb"); \
        if (fd != NULL){ \
          str = g_strdup_printf(format"\n"); \
          fprintf(fd, str); \
          DBG("-> Festival: |%s|", str); \
          free(str); \
          fclose(fd); \
        }else{ \
          DBG("Can't open connection"); \
        } \
    }

#define FEST_SEND_CMDA(format, args...) \
    { \
        FILE *fd; \
        char *str; \
        fd = fdopen(dup(info->server_fd),"wb"); \
        if (fd != NULL){ \
          str = g_strdup_printf(format"\n", args); \
          fprintf(fd, str); \
          DBG("-> Festival: |%s|", str); \
          free(str); \
          fclose(fd); \
        }else{ \
          DBG("Can't open connection"); \
        } \
    }


/* --- HELPER FUNCTIONS --- */
int
festival_check_info(FT_Info *info, char *fnname){
    assert(fnname != NULL);
    if ((info == NULL) || (info->server_fd == -1)){
        DBG("%s called with info = NULL or server_fd == -1\n", fnname);
        return -1;
    }
    return 0;
}


/***********************************************************************/
/* Public Functions to this API                                        */
/***********************************************************************/

/* Opens a connection to Festival server (which must be running)
 * and returns it's identification in new FT_Info */
FT_Info *
festivalOpen(FT_Info *info)
{
    char *resp;
    int ret;

    DBG("Openning socket fo Festival server");

    festival_connection_crashed = 0;

    if (info == 0)
	info = festivalDefaultInfo();

    info->server_fd = 
	festival_socket_open(info->server_host, info->server_port);

    if (info->server_fd == -1){
        delete_FT_Info(info);
	festival_connection_crashed = 1;
	return NULL;
    }


    FEST_SEND_CMD("(require 'speech-dispatcher)");
    ret = festival_read_response(info, &resp);
    if (ret || resp == NULL || strcmp(resp, "t\n")){
	DBG("ERROR: Can't load speech-dispatcher module into Festival."
	    "Reason: %s", resp);
	return NULL;
    }
    free(resp);

    FEST_SEND_CMD("(Parameter.set 'Wavefiletype 'nist)\n");
    ret = festival_read_response(info, &resp);
    if (ret || resp == NULL || strcmp(resp, "nist\n")){
	DBG("ERROR: Can't set Wavefiletype to nist in Festival. Reason: %s", resp);
	return NULL;
    }
    free(resp);

    return info;
}

int
festival_speak_command(FT_Info *info, char *command, const char *text, int symbol, int resp)
{
    FILE *fd;
    const char *p;
    char *str;
    int ret;

    if (festival_check_info(info, "festival_speak_command") == -1) return -1;
    if (command == NULL) return -1;
    if (text == NULL) return -1;

    DBG("(festival_speak_command): %s", text);

    /* Opens a stream associated to the socket */
    fd = fdopen(dup(info->server_fd), "wb");

    /* Send the command and data */
    if (symbol == 0)
      str = g_strdup_printf("(%s \"", command);
    else
      str = g_strdup_printf("(%s '", command);
    fprintf(fd, str);
    /* Copy text over to server, escaping any quotes */
    for (p=text; p && (*p != '\0'); p++)
    {
	if ((*p == '"') || (*p == '\\')) putc('\\',fd);
	putc(*p, fd);
    }
    if (symbol == 0) fprintf(fd,"\")\n");
    else fprintf(fd, ")\n");

    DBG("-> Festival: escaped text is %s", text);
    DBG("-> Festival: |%sthe text is displayed above\")|", str);

    free(str);
    /* Close the stream (but not the socket) */
    fclose(fd);
    DBG("Resources freed");

    if (resp){
	ret = festival_read_response(info, NULL);
	if (ret){
	    DBG("ERROR: Festival reported error in speak command);");
	    return -1;
	}
    }

    return 0;
}

#define FEST_SPEAK_CMD(name, cmd, symbol, resp) \
    int \
    name(FT_Info *info, const char *text) \
    { \
        return festival_speak_command(info, cmd, text, symbol, resp); \
    }

/* Sends a TEXT to Festival server for synthesis. Doesn't
 * wait for reply (see festivalStringToWaveGetData()). 
 * Please make sure that the socket is empty before calling
 * this command -- otherwise, you would get another message
 * in response.
 * Returns 0 if everything is ok, -1 otherwise.
*/
FEST_SPEAK_CMD(festivalStringToWaveRequest, "speechd-speak-ssml", 0, 1)
FEST_SPEAK_CMD(festivalSpell, "speechd-spell", 0, 1)
FEST_SPEAK_CMD(festivalSoundIcon, "speechd-sound-icon", 1, 0)
FEST_SPEAK_CMD(festivalCharacter, "speechd-character", 0, 0)
FEST_SPEAK_CMD(festivalKey, "speechd-key", 0, 0)

/* Reads the wavefile sent back after festivalStringToWaveRequest()
 * has been called. This function blocks until all the data is
 * available. Note that for longer messages this can be quite long
 * on some slower machines. */

FT_Wave*
festivalStringToWaveGetData(FT_Info *info)
{
    FT_Wave *wave = NULL;
    char ack[5];
    
    /* Read back info from server */
    /* This assumes only one waveform will come back, also LP is unlikely */
    do{
        if (festival_get_ack(&info, ack)) return NULL;
        DBG("<- Festival: %s", ack);
	if (strcmp(ack,"WV\n") == 0){
	    wave = client_accept_waveform(info->server_fd, NULL, 0);
	}else if (strcmp(ack,"LP\n") == 0){
	    client_accept_s_expr(info->server_fd);
	}else if (strcmp(ack,"ER\n") == 0){
            //  fprintf(stderr,"festival_client: server returned error\n");
            break;
        }
    }while (strcmp(ack,"OK\n") != 0);
    
    return wave;
}

FT_Wave*
festivalGetDataMulti(FT_Info *info, char **callback, int *stop_flag, int stop_by_close)
{
    FT_Wave *wave = NULL;
    char ack[5];
    char *resp = NULL;
    FILE *fd;

    if (festival_check_info(info, "festival_speak_command") == -1){	
	return NULL;	
    }

    /* Read back info from server */
    /* This assumes only one waveform will come back */
    wave = NULL;

    *callback = NULL;

    DBG("Stop by close mode : %d", stop_by_close);

    DBG("-> Festival: (speechd-next)");
    fd = fdopen(dup(info->server_fd),"wb");
    fprintf(fd, "(speechd-next)\n");
    fflush(fd);
    fclose(fd);

    do{
        if (festival_get_ack(&info, ack)){
	    DBG("Get ack failed");
	    return NULL;
	}
        DBG("<- Festival: %s", ack);

	if (strcmp(ack,"WV\n") == 0){
	    wave = client_accept_waveform(info->server_fd, stop_flag, stop_by_close);
	}else if (strcmp(ack,"LP\n") == 0){
	    if (resp != NULL) free(resp);
	    resp = client_accept_s_expr(info->server_fd);	    
	    if (resp == NULL){
		DBG("ERROR: Something wrong in communication with Festival, s_expr = NULL");
		return NULL;
	    }
	    if (strlen(resp) != 0) resp[strlen(resp)-1] = 0;
	    DBG("<- Festival: |%s|", resp);
	    if (!strcmp(resp, "nil")){
		DBG("festival_client: end of samples\n");
		free(resp);
		wave = NULL;
		resp = NULL;
	    }
	}else if (strcmp(ack,"ER\n") == 0){
            DBG("festival_client: server returned error\n");
            return NULL;
        }
    }while (strcmp(ack,"OK\n") != 0);

    if (resp){
	if ((strlen(resp) > 0) && (resp[0] != '#')) *callback = resp;
	else free (resp);
    }

    return wave;
}

/* Closes the Festival server socket connection */
int festivalClose(FT_Info *info)
{
    if (info == 0)
	return 0;

    if (info->server_fd != -1){
        FILE *fd;
        fd = fdopen(dup(info->server_fd),"wb");
        if (fd != NULL){
            fprintf(fd,"(quit)\n");
            fclose(fd);
        }
	close(info->server_fd);
    }

    return 0;
}

/* --- INFORMATION RETRIEVAL COMMANDS --- */

char**
lisp_list_get_vect(char* expr)
{
  size_t len;
  char *helper;
  gchar **vect;
  size_t i,j;

  len = strlen(expr);
  helper = xmalloc(sizeof(char) * (len+1));

  //Remove parenthesis
  j=0;
  for (i=0; i<len; i++){
    if ((expr[i] != '(') && (expr[i] != ')')){
      helper[j] = expr[i];
      j++;
    }
  }
  helper[j]='\0';

  // Split into a vector of atoms
  vect = g_strsplit(helper, " ", 0);

  return vect;
}

char* vect_read_item(char **vect, char* field)
{
  int i; 

  for (i=0; ;i++){
    if (vect[i] == NULL)
      return NULL;
    else
      {
	if (!strcmp(vect[i], field)){
	  if (vect[i+1] != NULL) return vect[i+1];
	  else return NULL;
	}
      }
  }
}


VoiceDescription** festivalGetVoices(FT_Info *info)
{
  char *reply;
  char** voices;
  char* lang; char* dialect;
  int i, j;
  int num_voices = 0;
  VoiceDescription** result;

  FEST_SEND_CMD("(apply append (voice-list-language-codes))");
  festival_read_response(info, &reply);
  if (reply == NULL){
    DBG("ERROR: Invalid reply for voice-list");
    return NULL;
  }
  /* Remove trailing newline */
  reply[strlen(reply)-1]=0;
  DBG("Voice list reply: |%s|", reply);
  voices = lisp_list_get_vect(reply);
  if (voices == NULL){
    DBG("ERROR: Can't parse voice listing reply into vector");
    return NULL;
  }
  
  /* Compute number of voices */
  for (i=0; ; i++, num_voices++) if (voices[i] == NULL) break;
  num_voices /= 3;  
  
  result = (VoiceDescription**) malloc((num_voices + 1)*sizeof(VoiceDescription*));
  
  for (i=0, j=0; ;j++){
    if (voices[i] == NULL)
      break;
    else if (strlen(voices[i]) == 0)
      continue;
    else
      {
	result[j] = (VoiceDescription*) malloc(sizeof(VoiceDescription));
	result[j]->name = voices[i];
	lang = voices[i+1];
	if ((lang != NULL) && (strcmp(lang, "nil")))
	  result[j]->language = strdup(lang);
	else
	  result[j]->language = NULL;
	dialect = voices[i+2];
	if ((dialect != NULL) && (strcmp(dialect, "nil")))
	  result[j]->dialect = strdup(dialect);
	else
	  result[j]->dialect=NULL;
	i+=3;
      }
  }
  result[j] = NULL;
  return result;
}

/* --- SETTINGS COMMANDS --- */

#define FEST_SET_STR(name, fest_param) \
    int \
    name(FT_Info *info, char *param, char **resp) \
    { \
        char *r; \
        int ret; \
        char *f; \
        if (festival_check_info(info, #name)) return -1; \
        if (param == NULL){ \
	  FEST_SEND_CMD("("fest_param" nil)"); \
        }else{ \
          f = g_ascii_strdown(param, -1); \
	  FEST_SEND_CMDA("("fest_param" \"%s\")", f); \
          xfree(f); \
	} \
        ret = festival_read_response(info, &r); \
        if (ret != 0) return -1; \
        if (r != NULL){ \
          if (resp != NULL) \
             *resp = r; \
          else \
             free(r); \
        } \
        return ret; \
    }

#define FEST_SET_SYMB(name, fest_param) \
    int \
    name(FT_Info *info, char *param) \
    { \
        char *f = NULL; \
        if (festival_check_info(info, #name)) return -1; \
        if (param == NULL) return -1; \
        FEST_SEND_CMDA("("fest_param" '%s)", f = g_ascii_strdown(param, -1)); \
        xfree(f); \
        return festival_read_response(info, NULL); \
    } 

#define FEST_SET_INT(name, fest_param) \
    int \
    name(FT_Info *info, int param) \
    { \
        if (festival_check_info(info, #name)) return -1; \
        FEST_SEND_CMDA("("fest_param" %d)", param); \
        return festival_read_response(info, NULL); \
    }

FEST_SET_SYMB(FestivalSetMultiMode, "speechd-enable-multi-mode")

FEST_SET_INT(FestivalSetRate, "speechd-set-rate")
FEST_SET_INT(FestivalSetPitch, "speechd-set-pitch")
FEST_SET_SYMB(FestivalSetPunctuationMode, "speechd-set-punctuation-mode")
FEST_SET_STR(FestivalSetCapLetRecogn, "speechd-set-capital-character-recognition-mode")
FEST_SET_STR(FestivalSetLanguage, "speechd-set-language")
FEST_SET_STR(FestivalSetVoice, "speechd-set-voice")
FEST_SET_SYMB(FestivalSetSynthesisVoice, "speechd-set-festival-voice")


static FT_Info *festivalDefaultInfo()
{
    FT_Info *info;
    info = (FT_Info *) malloc(sizeof(FT_Info));
    
    info->server_host = FESTIVAL_DEFAULT_SERVER_HOST;
    info->server_port = FESTIVAL_DEFAULT_SERVER_PORT;
    info->text_mode = FESTIVAL_DEFAULT_TEXT_MODE;

    info->server_fd = -1;
    
    return info;
}

/* For testing purposes, this library can be compiled as
 * a standalone program, please see the introduction at
 * the beginning  of this file
 */

#ifdef STANDALONE
int main(int argc, char **argv)
{
    char *server=0;
    int port=-1;
    char *text=0;
    char *output=0;
    char *mode=0;
    int i;
    FT_Info *info;
    FT_Wave *wave;

    printf("Welcome to Festival client library for Speech Dispatcher\n");

    info = festivalDefaultInfo();
    if (server != 0)
	info->server_host = FESTIVAL_DEFAULT_SERVER_HOST;
    if (port != -1)
	info->server_port = FESTIVAL_DEFAULT_SERVER_PORT;
    if (mode != 0)
	info->text_mode = FESTIVAL_DEFAULT_TEXT_MODE;

    info = festivalOpen(info);

    if (info == 0){
        printf("Can't open connection to festival server!\n");
        exit(1);
    }
    if (info->server_fd <= 0){
        printf("Can't open connection to festival server!\n");
        exit(1);
    }

    festivalStringToWaveRequest(info,"Hi, how are you? I'm Festival! And who are you?");
    wave = festivalStringToWaveGetData(info);

    if (wave != 0){
	save_FT_Wave_snd(wave,"test.snd");
    }else{
        printf("Wave null! Festival server doesn't work properly.\n");
        exit(1);
    }
    festivalClose(info);

    printf("The synthesized text should have been written to test.snd\n");

    return 0;
}
#endif

